module qtrt


/*
#cgo CFLAGS: -std=c11
#cgo LDFLAGS: -ldl

extern void* vtablehook_hook(void* instance, void* hook, int offset);
extern void test_vthook(void* app);
extern void* find_f_address(void* aClass, void* memfnptr);
extern void dump_vtable_entry(void* elfvtptr);
extern int fillin_vtable_entry(void* elfvtptr, void** arr);

void
cxa_demangle_asmcc(struct {void* fnptr; const char *mangled; char *buf; size_t *len; int *status;} *ax) {
   void* (*fnptr2)() = ax->fnptr;
   char* res = fnptr2(ax->mangled, ax->buf, ax->len, ax->status);
   // printf("%p ***%s***\n", res, ax->mangled);
   *(ax->len) = res == NULL ? 0 : strlen(res);
}

*/
/*
import "C"
import (
	"fmt"
	"go/scanner"
	"go/token"
	"log"
	"strings"
	"sync"

	"github.com/kitech/dl/asmcgocall"
)

func TestVthook(app Voidptr) {
	C.test_vthook(app)
}

// 这种方式似乎对dlsym取到的memfnptr不适用？
func FindVtmAddr(obj Voidptr, memfnptr Voidptr) Voidptr {
	log.Println(obj, memfnptr)
	return C.find_f_address(obj, memfnptr)
}
*/

import sync


#flag @VROOT/qtrt/vthook3.o
#flag @VROOT/qtrt/vthook.o
#flag @VROOT/qtrt/ffi_invoke.o

// #flag -std=c11
// #flag -ldl

#include "@VROOT/qtrt/vthook.h"

///// 这种方式似乎对linux, .so有效
fn C.dump_vtable_entry() int

pub fn dumpVtableEntry(clsname string) {
    vtname := "_ZTV${clsname.len}${clsname}"
	ptr := sym_cfunc6(0, vtname)
	//log.Println(vtname, ptr)
	C.dump_vtable_entry(ptr)
}

fn C.fillin_vtable_entry() int

pub fn getVtableEntry(clsname string) []string {
	vtname := "_ZTV${clsname.len}${clsname}"
	ptr := sym_cfunc6(0, vtname)
    // println("$vtname, $ptr")

	items := [128]voidptr{}
	cnt := C.fillin_vtable_entry(ptr, &items[0])
    // println("$clsname vtecnt $cnt")
	// items = items[0..int(cnt)]

    mut items2 := []string{}
    for idx := 0; idx < cnt; idx ++ {
        item := items[idx]
        sym := if item == vnil { "" } else { tos_clone(item) }
        items2 << sym
        // println(sym)
    }

	return items2
}

// thread safe???
// lazy fillin for requested class
struct VirtualTable {
    mut:
	mu     &sync.RwMutex = sync.new_rwmutex()
	tables map[string]&VtableEntry // clsname =>
}

struct VtableEntry {
    mut:
	clsname string
	items   []string              // mangled name
	names   []string              // just pure pub fn name like `mouseMoveEvent`
    cmos    []&Cppmethod
	hooks   map[string]&HookEntry // mthname =>
}

struct HookEntry {
    mut:
	mgname  string
	rawname string
	index   int
	oldfn   voidptr
	newfn   voidptr
	vmclos  &CppvmClosure = vnil
}

pub const virtab = newVirtualTable()

pub fn newVirtualTable() &VirtualTable {
	mut this := &VirtualTable{}
	this.tables = map[string]&VtableEntry{}
	return this
}

fn C.vtablehook_hook() voidptr

// fn for under closure types resolve
pub fn (this &VirtualTable) hookit(clsname, mthname string, obj voidptr, fun voidptr) {
	if this.hookExist(clsname, mthname) {
		return
	}
	idx := this.getVTMIndex(clsname, mthname)
	if idx == -1 {
		infoln(@FILE, @LINE, "not found ${clsname}, ${mthname}")
		return
	}

    cmo := this.getCppmethod(clsname, mthname, idx)
	vmclos := newCppvmClosure(clsname, mthname, fun, cmo.types)
	fnptr := vmclos.getFunc()

	//hookimpl()
    infoln(@FILE, @LINE, "hooking", clsname, mthname, obj, fnptr, idx)
	oldfn := C.vtablehook_hook(obj, fnptr, int(idx))
    infoln(@FILE, @LINE, "hooked", clsname, mthname, obj, fnptr, idx)
	mut he := &HookEntry{}
	he.mgname = ""
	he.rawname = mthname
	he.index = idx
	he.oldfn = oldfn
	he.newfn = fnptr
	he.vmclos = vmclos

    mut vte := this.tables[clsname]
	vte.hooks[mthname] = he
}

pub fn (this &VirtualTable) unhookit(clsname, mthname string, obj voidptr) {
	if !this.hookExist(clsname, mthname) {
		return
	}

    mut vte :=  this.tables[clsname]
    mut he := vte.hooks[mthname]
	oldfn := C.vtablehook_hook(obj, he.oldfn, int(he.index))
	if oldfn != he.newfn {
		// wtt
	}
    vte.hooks.delete(mthname)
}

pub fn (this &VirtualTable) hookExist(clsname, mthname string) bool {
    if clsname in this.tables {
        if mthname in this.tables[clsname].hooks {
            return true
        }
    }
    return false
}

pub fn (this &VirtualTable) getVTMIndex(clsname, mthname string) int {
    if clsname !in this.tables {
        this.fillinClassVtable(clsname)
    }
    mut vte := this.tables[clsname]

	// TODO overloaded resolution???
	for idx, name in vte.names {
		if name == mthname {
			return idx
		}
	}
	return -1
}

fn (this &VirtualTable) getCppmethod(clsname, mthname string, index int) &Cppmethod {
    mut vte := this.tables[clsname]
    return vte.cmos[index]
}

pub fn (thisp &VirtualTable) fillinClassVtable(clsname string) voidptr {
    mut this := thisp
    if clsname in this.tables {
        return vnil
    }
	items := getVtableEntry(clsname)

	mut e := &VtableEntry{}
	e.clsname = clsname
	e.items = items
	e.hooks = map[string]&HookEntry{}
	e.names = []string{len: items.len}
    e.cmos  = []&Cppmethod{len: items.len}

	for idx, item in items {
		//println("$idx, $item")
        cmo := cppfilt(item)
		e.names[idx] = cmo.mth
        e.cmos[idx] = cmo
	}

	this.tables[clsname] = e

	return vnil
}

struct Cppmethod {
    mut:
	cls     string
	mth     string
	types   []string
	names   []string
	isconst bool
}

pub fn (this &Cppmethod) isctor() bool {
	return this.cls != "" && (this.cls == this.mth)
}

pub fn (this &Cppmethod) isdtor() bool {
    return this.mth.starts_with("~")
}

pub fn (this &Cppmethod) ismth() bool {
	return this.cls != ""
}

pub fn (this &Cppmethod) argc() int { return this.types.len }
pub fn (this &Cppmethod) argisref(idx int) bool {
	return this.types[idx].count("&") == 1
}
pub fn (this &Cppmethod) argisptr(idx int) bool {
    return this.types[idx].count("*") == 1
}

// char* qil_cxa_demangle(const char *mangled, char *buf, size_t *len, int *status)

type Tqil_cxa_demangle = fn(byteptr, byteptr, &size_t, &int) byteptr

pub fn cxa_demangle(mgname string) (string, string) {
	mgname2 := mgname // + "\x00"
	mgnameref := mgname.str // CStringRefRaw(&mgname2)

	mut blen := mgname.len *3 + 3
	mut buf := []byte{len: blen}
	status := 0

    mut fnptr := Tqil_cxa_demangle(0)
    fnptr = sym_cfunc6(0, "qil_cxa_demangle")
    unsafe {
        fnptr(mgnameref, &buf[0], &blen, &status)
    }
	if status != 0 {
		return "", "demangle error ${status}"
	}

    pos := buf.index(byte(0))
    assert pos != -1
    if pos != blen { blen = pos }
    mut res := unsafe {tos(&buf[0], blen)}
    res = res.clone()
	mut hintpfx := "non-virtual thunk to " // _ZThn
	if res.starts_with(hintpfx) {
        res = res.substr(hintpfx.len, res.len)
	}
	hintpfx = "typeinfo for "
	if res.starts_with(hintpfx) {
        res = res.substr(hintpfx.len, res.len)
	}
	return res, ""
}

fn cppfilt(mgname string) &Cppmethod {
	mut cmo := &Cppmethod{}
    if mgname.len == 0 {
        return cmo
    }

    // infoln(@FILE, @LINE, mgname)
	// line, err := demangle.ToString(mgname)
	mut line, err := cxa_demangle(mgname)
    if err != "" {
        infoln(@FILE, @LINE, "some err $err '$mgname'")
        return cmo
    }
    if mgname.starts_with("_ZTI") {
        cmo.cls = line
        return cmo
    }

    mut pos1 := line.index("::") or {-1}
    pos2 := line.index("(") or {-1}
    pos3 := line.last_index(")") or {-1}
    // println("$line $pos1, $pos2, $pos3")

    // parse class/method
    cmo.cls = if pos1 == -1 {""} else { line[0..pos1] }
    pos1 = if pos1 == -1 { 0 } else { pos1}

    // member object, not method
    // QAbstractButton::staticMetaObject 15, -1, -1
    if pos2 == -1 {
        cmo.mth = line[(pos1+2)..line.len]
        return cmo
    }
    cmo.mth = line[(pos1+2)..pos2]

    // parse arguments
    prmline := line[pos2+1..pos3]
    prmtys := prmline.split(", ")
    cmo.types = prmtys

    line = line.trim_right(" &")
    cmo.isconst = line.ends_with(" const")
    // println("$line, $cmo")

    return cmo
}
